package im.composer.midi.seq;

import java.util.Arrays;
import java.util.List;

import javax.sound.midi.InvalidMidiDataException;
import javax.sound.midi.MidiEvent;
import javax.sound.midi.MidiMessage;
import javax.sound.midi.MidiUnavailableException;
import javax.sound.midi.Receiver;
import javax.sound.midi.Sequence;
import javax.sound.midi.Track;
import javax.sound.midi.Transmitter;

import org.tritonus.share.midi.TSequencer;

public class VirtualSequencer extends TSequencer implements Runnable, Receiver {

	private transient SequencingUtils.TempoCache tempo_cache;
	private transient Track[] tracks;
	private transient int[] track_index;
	private transient long currentTime = -1;
	private transient long last_tick_position;
	private transient long this_tick_position;
	private Transmitter transmitter;
	// variables for recording
	private boolean recording = false;
	private long recordPosition = 0;
	private Sequence rec_seq;
	private Track rec_track;

	public VirtualSequencer() {
		super(null, Arrays.<SyncMode> asList(), Arrays.asList(SyncMode.NO_SYNC));
		tempo_cache = new SequencingUtils.TempoCache();
		try {
			transmitter = super.getTransmitter();
		} catch (MidiUnavailableException e) {
		}
	}

	@Override
	public void run() {
		if (transmitter.getReceiver() == null) {
			return;
		}
		if(isRunning()&&tracks==null){
			startImpl();
		}
		for (int i = 0; i < tracks.length; i++) {
			Track track = tracks[i];
			if (track_index[i] >= track.size()) {
				continue;
			}
			for (int j = track_index[i]; j < track.size(); j++) {
				MidiEvent evt = track.get(j);
				long tick = evt.getTick();
				if (tick >= last_tick_position && tick < this_tick_position) {
					send(evt.getMessage(), currentTime);
					track_index[i] = j;
				}
			}
		}
		last_tick_position = this_tick_position;
	}

	public void setMicrosecondPosition(long lPosition) {
		long tick_pos = microsecond2tick(lPosition);
		setTickPosition(tick_pos);
		currentTime = lPosition;
	}
	
	private long microsecond2tick(long lPosition){
		return SequencingUtils.microsecond2tick(getSequence(), lPosition, tempo_cache);
	}

	@Override
	public long getMicrosecondPosition() {
		return currentTime;
	}

	public void setTickPosition(long lPosition) {
		// if (this_tick_position < last_tick_position) {
		// throw new
		// java.lang.IllegalArgumentException("this_tick_position<last_tick_position");
		// }
		this_tick_position = lPosition;
	}

	public long getTickPosition() {
		return this_tick_position;
	}

	public void resetPosition(){
		currentTime = last_tick_position = this_tick_position = 0;
		for(int i=0;i<tracks.length;i++){
			track_index[i]=0;
		}
	}
	
	@Override
	protected void startImpl() {
		last_tick_position = this_tick_position = 0;
		if(getSequence()==null){
			try {
				setSequence(new FreeSequence(Sequence.PPQ, 120));
			} catch (InvalidMidiDataException e) {
			}
		}
		tracks = getSequence().getTracks();
		track_index = new int[tracks.length];
	}

	@Override
	protected void stopImpl() {
		last_tick_position = this_tick_position = 0;
		if (transmitter != null) {
			transmitter.close();
		}
	}

	@Override
	protected void setSequenceImpl() {
		tempo_cache.refresh(getSequence());
	}

	public Transmitter getTransmitter() {
		return transmitter;
	}

	@Override
	public Receiver getReceiver() throws MidiUnavailableException {
		return this;
	}

	@Override
	public List<Receiver> getReceivers() {
		return Arrays.<Receiver>asList(this);
	}

	@Override
	public List<Transmitter> getTransmitters() {
		return Arrays.<Transmitter>asList(transmitter);
	}

	@Override
	public void send(MidiMessage message, long timeStamp) {
		Receiver rcv = transmitter.getReceiver();
		if (rcv != null) {
			rcv.send(message, timeStamp);
		}
		if (recording && rec_track != null) {
			MidiEvent event = new MidiEvent(message, microsecond2tick(timeStamp+recordPosition));
			rec_track.add(event);
		}
	}

	public long getRecordPosition() {
		return recordPosition;
	}

	/**
	 * 录制MIDI的起始时间
	 * @param recordPosition
	 */
	public void setRecordPosition(long recordPosition) {
		this.recordPosition = recordPosition;
	}

	public void recordDisable(Track track) {
		throw new java.lang.UnsupportedOperationException("Not Implemented Yet!");
	}

	public synchronized void recordEnable(Track track, int nChannel) {
		if(nChannel!=-1){
			throw new java.lang.IllegalArgumentException("Channel limit is not implemented!");
		}
		if(Arrays.asList(rec_seq.getTracks()).contains(track)){
			rec_track = track;
		}else if(rec_seq instanceof FreeSequence){
			((FreeSequence) rec_seq).add(track);
		}else{
			throw new java.lang.IllegalArgumentException("Track not supported!");
		}
	}

	public synchronized void startRecording() {
		if(rec_seq==null){
			try {
				rec_seq = new FreeSequence(Sequence.PPQ, 120);
				rec_track = rec_seq.createTrack();
			} catch (InvalidMidiDataException e) {
			}
		}
		recording = true;
	}

	public void stopRecording() {
		recording = false;
	}

	public boolean isRecording() {
		return recording;
	}

	public void setRecordedSequence(Sequence rec_seq) {
		this.rec_seq = rec_seq;
	}

	public Sequence getRecordedSequence() {
		return rec_seq;
	}

	protected void setTempoImpl(float fMPQ) {
	}

}